/**************************************************************
 * ui_cli.c - command line user interface module
 * Copyright (c) 2009  Qin Xiaoxue  <crs.chin@gmail.com>
 *
 * This file is  free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
**************************************************************/


#include <stdio.h>
#include <errno.h>
#include <dlfcn.h>
#include <string.h>
#include <malloc.h>
#include <time.h>
#include <ctype.h>
#include <signal.h>
#include <assert.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <sys/select.h>

#include "common.h"
#include "ui_cli.h"
#include "configfile.h"
#include "ui.h"
#include "client.h"
#include "fetchlyric.h"
#include "lyricparse.h"
#include "encoding_convert.h"
#include "argproc.h"
#include "metainfo.h"

struct ui_data{
  struct client *client;
  const gchar *client_plugin;
  fetchlyric_t *lyric;
  config_t *config;
};

struct command{
  const gchar *long_cmd;
  const gchar *short_cmd;
  const gchar *descript;
  lyric_t *(*func)(struct client *client, fetchlyric_t *fl, const gchar *arg);
};


static gboolean volatile keep_rolling = TRUE;
static gboolean volatile fall_to_shell = FALSE;
static gboolean wait_if_no_lyric = TRUE;
static gboolean exit_on_quit = FALSE;
static gint     volatile magix = 0;	/* marking player status changes */


static int __op_on_term(char *cmd)
{
  char *s = tigetstr(cmd);
  if(!s)
    return FALSE;
  putp(s);
}


static void __set_foreground(int color)
{
  char *s, *colorcmd;

  s = tigetstr("setaf");
  colorcmd = tparm(s, color);
  putp(colorcmd);
}

static void __set_background(int color)
{
  char *s, *colorcmd;

  s = tigetstr("setab");
  colorcmd = tparm(s,color);
  putp(colorcmd);

}


static void qcursor_goto(int row, int col)
{
  char *s,*cmd;

  s = tigetstr("cup");
  cmd = tparm(s, row, col);
  putp(cmd);
}


static void qdelete_line()
{
  int i=0,j;

  j=tigetnum("cols");
  putchar('\r');
  for(;i<j;i++)
    putchar(' ');
  putchar('\r');
}



void print_lyric(char *s, int fgcolor, int bgcolor, int normalfg, int normalbg,	 int offset)
{
  char *t = NULL;
  int cols, i;

  assert(offset >= 0);
  assert(offset <= strlen(s));

  cols = COLUMNS;
  t = (char *)malloc(offset+1);
  strncpy(t, s, offset);
  t[offset] = '\0';

  __set_foreground(fgcolor);
  __set_background(bgcolor);

  qcursor_save();
  for(i=0; i < strlen(s) / cols + 1; i++) {
    qcursor_up();
    qdelete_line();
  }

  putp(t);
  free(t);

  /* restoring colors */
  __set_foreground(normalfg);
  __set_background(normalbg);
  putp(s+offset);
  fflush(stdout);
  qcursor_restore();
}



void print_moving_lyric(gchar *s, gint fg, gint bg,	gint elapsed, gint timewithin, gint magic)
{
  gint interval;
  gint t = elapsed;
  int offset=0;
  int len, i, cols;
  struct timespec ts;

  if(elapsed > timewithin)  return;

  ts.tv_sec = 0;
  if(! s)
	s = "";
  len = strlen(s);
  interval = RESPONSE_TIME;

  DBGMSG("timewithin:%d\n",timewithin);

  ts.tv_nsec = interval * 1000000;
  ts.tv_sec+=ts.tv_nsec / 1000000000;
  ts.tv_nsec=ts.tv_nsec % 1000000000;
  qcursor_invisible();

  DBGMSG("tv_sec:%d,tv_nsec:%d\n",ts.tv_sec,ts.tv_nsec);
  DBGMSG("tv_sec:%d,tv_nsec:%d\n",ts.tv_sec,ts.tv_nsec);

  while(t <= timewithin  && magic == magix) {
	if(t + interval >= timewithin)
	  offset = len;
	else
	  offset = (int)(((float)t / timewithin) * len);
    print_lyric(s, FOREGROUND_COLOR, BACKGROUND_COLOR, NORMAL_FG_COLOR, NORMAL_BG_COLOR, offset);
    putchar('\r');
    fflush(stdout);
    t += interval;
    nanosleep(&ts, NULL);
	if(! keep_rolling)
	  break;
  }

  cols=COLUMNS;
  for(i=0; i<strlen(s)/cols+1; i++) {
    qscroll_up();
    qdelete_line();
  }
  __set_foreground(fg);
  __set_background(bg);
  qcursor_normal();
}




/*
 *GLOBAL ARGS FOR UI MODULES:
 *   client_basedir       (string(dir))
 *   lyric_basedir        (string(dir))
 *   client_backend       (string(NAME))
 *   client_backend_file  (string(FILE))
 *   client_name          (string(NAME))
 *   lyric_plugin         (string(all, NAME))
 *   lyric_plugin_file    (string(FILE))
 */

static struct ui  *ui_cli_init(const gchar *args, gint argc, gchar **argv)
{
  struct ui *ui = NULL;
  struct ui_data *data;
  struct client *client;
  fetchlyric_t *fl = NULL;

  gchar *client_dir, *lyric_dir;
  gchar *client_plugin, *lyric_plugin;
  gchar *cp_file, *lp_file;
  gchar *client_name;
  config_t *config;
  gchar *tmp = NULL;

  if(! isatty(fileno(stdout)))  {
    print_err("Refuse to display lyrics non console files.\n");
    return NULL;
  }

  client_dir = parse_arg_str("client_basedir", args);
  lyric_dir = parse_arg_str("lyric_basedir", args);

  client_plugin = parse_arg_str("client_backend", args);
  lyric_plugin = parse_arg_str("lyric_plugin", args);

  cp_file = parse_arg_str("client_basckend_file", args);
  lp_file  = parse_arg_str("lyric_plugin_file", args);

  client_name = parse_arg_str("client_name", args);

  config = load_config(NULL, "ui", "ui_cli.conf", UI_CLI_DEFAULT_CONFIG, 0, TRUE);
  if(! config)  {
	fprintf(stderr, "** unable to load configurations.");
	if(! (( client_plugin || cp_file) &&
		  (lyric_plugin  || lp_file)))  {
	  fprintf(stderr, "failed to init UI module, abort...\n");
	  free_config(config);
	  goto cleanup;
	}
  }else  {
	if(! client_dir)
	  read_config(config, NULL, "client_basedir", &client_dir, NULL, TRUE );
	if(! lyric_dir)
	  read_config(config, NULL, "lyric_basedir", &lyric_dir, NULL, TRUE );
	if(! (client_plugin || cp_file))
	  read_config(config, NULL, "client_backend", &client_plugin, "XMMS2", TRUE );
	if(! (lyric_plugin || lp_file))
	  read_config(config, NULL, "lyric_plugin", &lyric_plugin, "all", TRUE );
	if(! client_name)
	  read_config(config, NULL, "client_name", &client_name, PROG_NAME, TRUE );

	read_config(config, NULL, "no_lyric_wait", &tmp, "YES", TRUE );
	if(strcasecmp(tmp, "YES"))
	  wait_if_no_lyric = FALSE;
	else
	  wait_if_no_lyric = TRUE;
	g_free(tmp);

	read_config(config, NULL, "exit_on_quit", &tmp, "NO", TRUE );
	if(strcasecmp(tmp, "YES"))
	  exit_on_quit = FALSE;
	else
	  exit_on_quit = TRUE;
	g_free(tmp);
  }


  if(client_plugin)
	client = load_and_init_client(client_plugin, client_name, NULL, argc, argv);
  else
	client = load_and_init_client_file(cp_file, client_name, NULL, &client_plugin, argc, argv);
  if(! client)  {
    fprintf(stderr, "** Fatal, unable to load player backend!\n");
	free_config(config);
	goto cleanup;
  }

  do{
	if(lyric_plugin)  {
	  if(! strcmp(lyric_plugin, "all"))  {
		if(fetch_lyric_load_all(&fl, lyric_dir, argc, argv))
		  break;
		fprintf(stderr, "** Unable to load any fetchlyric plugin, abort...\n");
	  }else  {
		if(fetch_lyric_load_plugin(&fl, lyric_plugin, lyric_dir, argc, argv))
		  break;
		fprintf(stderr, "** Unable to lyric plugin %s, aborting...\n", lyric_plugin);
	  }
	}else  {
	  if(! fetch_lyric_load_plugin_from_file(&fl, lp_file, argc, argv))
		break;
	  fprintf(stderr, "** Unable to lyric plugin %s, aborting...\n", lp_file);
	}

	unload_client(client_plugin, client);
	free_config(config);
	goto cleanup;
  }while(0);


  data = g_new0(struct ui_data, 1);
  data->client = client;
  data->client_plugin = strdup(client_plugin);
  data->lyric = fl;
  data->config = config;

  ui = g_new0(struct ui, 1);
  ui->ui_run = ui_cli_run;
  ui->ui_cleanup = ui_cli_cleanup;
  ui->data = (void *)data;

 cleanup:
  if(client_dir)
	g_free(client_dir);
  if(lyric_dir)
	g_free(lyric_dir);
  if(client_plugin)
	g_free(client_plugin);
  if(lyric_plugin)
	g_free(lyric_plugin);
  if(cp_file)
	g_free(cp_file);
  if(lp_file)
	g_free(lp_file);
  if(client_name)
	g_free(client_name);

  return ui;
}


/* timeout must be greater than 0, or NULL will be returned, or chose
   another func */
static gchar *readline_timeout(const gchar *prompt, gint timeout)
{
  gchar *line = NULL, *p;
  gint cnt, totaltime = timeout;
  struct timeval tv, *ptv;
  fd_set readset;

  while(timeout > 0)   {
	tv.tv_sec = 1;
	tv.tv_usec = 0;
	ptv = &tv;

	printf(prompt, timeout);
	fflush(stdout);
	FD_ZERO(&readset);
	FD_SET(fileno(stdin), &readset);
	if(select(fileno(stdin) + 1, &readset, NULL, NULL, ptv) > 0)  {
	  if(FD_ISSET(fileno(stdin), &readset))
		getline(&line, &cnt, stdin);
	  else
		continue;

	  p = line;
	  if(p)
		while(*p)  {
		  if(*p == '\n')
			*p = '\0';
		  p++;
		}

	  if(! line || ! *line)  {
		free(line);
		line = NULL;
		timeout = totaltime;
		continue;
	  }

	  break;
	}else if(errno != EINTR)  {
	  timeout--;
	  errno = ETIME;
	  continue;
	}
  }

  return line; 
}


static gint prompt_for_index_timeout(gint from, gint to, gint def, gint timeout)
{
  gint i = 0, index = 1;
  gchar *str;
  const gchar *prompt;

  putchar('\n');
  prompt = g_strdup_printf("\rselect lyric index within(%d,%d) in %%d seconds, -1 to cancel:>", from, to);
  while(str = readline_timeout(prompt, timeout))  {
    if(sscanf(str, "%d", &i))
      if(i >= from && i <= to || i == -1)  {
		index = i;
		errno = 0;
		break;
      }
    g_free(str);
    printf("\nINFO:please input number between %d and %d, -1 to abort.\n", from, to);
	}

  if(errno == ETIME)
    index = def;

  g_free(str);
  g_free((void *)prompt);
  putchar('\n');
  return index;
}


static gint __get_term_width()
{
  struct winsize sz;

  if(ioctl(1, TIOCGWINSZ, &sz) == -1)
    return 0;

  return sz.ws_col;
}



static gboolean __is_illegal(gchar c, const gchar *str)
{
  while(*str)
	if(c == *str++)
	  return TRUE;

  return FALSE;
}

static gchar  *__sanify(gchar *str)
{
  gchar *p, *i, *j;

  if(! str)
    return NULL;

  p = g_new0(gchar, strlen(str) + 1);
  i = p;
  j = str;
  while(*j)  {
    if(! __is_illegal(*j, " \t~`!@#$%^&*()-_+=|\\{}[]:\";\'<>?,./"))  {
	  *i = *j;
	  i++;
	}
    j++;
  }

  strcpy(str, p);
  g_free(p);

  return str;
}


static lyric_t *prompt_pick_lyric(GSList *list, const gchar *title, const gchar *artist)
{
  GSList *iter = list;
  struct lyric_url *info;
  gint title_len = 0, artist_len = 0, album_len = 0;
  gint index, tmp, term_width;
  FILE *lyric_file;
  lyric_t *lyric = NULL;
  gchar *_artist;

  if(! list)
    return NULL;

  do{
    info = (struct lyric_url *)iter->data;
    if(info->title)  {
      tmp = strlen(info->title);
      if(tmp > title_len)
		title_len = tmp;
    }

    if(info->artist)  {
      tmp = strlen(info->artist);
      if(tmp > artist_len)
		artist_len = tmp;
    }

    if(info->album)  {
      tmp = strlen(info->album);
      if(tmp > album_len)
		album_len = tmp;
    }
  }while(iter = g_slist_next(iter));

  iter = list;
  tmp = 1;
  term_width = __get_term_width();
  title_len++;
  artist_len++;
  album_len++;
  if(! term_width || term_width <= (title_len + artist_len + album_len + 13))  {
    /* if terminal unknown, assume it big enough */
    printf("\n%5s%*s    %*s    %*s\n", "NO.", title_len, "TITLE",
		   artist_len, "ARTIST", album_len, "ALBUM");
    do{
      info = (struct lyric_url *)iter->data;
      printf("[%-2d] %-*s   %-*s    %-*s\n", tmp++,
			 title_len, info->title ?: "Unknown",
			 artist_len, info->artist ?: "Unknown", 
			 album_len, info->album ?: "Unknown");

    }while(iter = g_slist_next(iter));
  }else  {
    printf("\n%-5s%-*s    %-*s\n", "NO.", title_len, "TITLE", artist_len, "ARTIST");
    do{
      info = (struct lyric_url *)iter->data;
      printf("[%-2d] %-*s    %-*s\n", tmp++,
			 title_len, info->title ?: "Unknown",
			 artist_len, info->artist ?: "Unknown");
    }while(iter = g_slist_next(iter));
  }

  index = prompt_for_index_timeout(1, tmp - 1, 0, 5);
  if(index == -1)
	return NULL;
  DBGMSG("you picked %d\n", index);

  if(index == 0)  {
	iter = list;
	do{
	  info = (struct lyric_url *)iter->data;
	  if(artist && info->artist)  {
		_artist = strdup(info->artist);
		__sanify(_artist);
		if(! strcasecmp(artist, _artist))  {
		  g_free(_artist);
		  break;
		}
		g_free(_artist);
	  }
	}while(iter = g_slist_next(iter));
	if(! iter)
	  iter = list;
  }else
	iter = g_slist_nth(list, index - 1);

  if(iter)  {
    lyric_file = fetch_lyric_file_next(iter);
    if(lyric_file)  {
      lyric = lyricparse_parse_lyric(lyric_file);
	  if(! lyric)
		printf("BUG: FAILED PARSING LYRIC FILE\n");
      fclose(lyric_file);
      return lyric;
    }
  }
  return NULL;
}


static lyric_t *__pick_right_lyric(fetchlyric_t *fl, const gchar *title, const gchar *artist)
{
  lyric_t *lyric = NULL;
  FILE *lyric_file;
  GSList *list;

  list = fetch_lyric_get_list_from(fl, "localdir", title, artist);
  if(! list)
    list = fetch_lyric_get_list(fl, title, artist);

  if(! list)
    return NULL;

  if(g_slist_length(list) > 1)
    lyric = prompt_pick_lyric(list, title, artist);
  else  {
    lyric_file = fetch_lyric_file_next(list);
    if(lyric_file)  {
      lyric = lyricparse_parse_lyric(lyric_file);
      fclose(lyric_file);
    }
  }

  if(! fetch_lyric_free_list(list)) {
    DBGMSG("failed to free lyric list:%s:%d\n", __FILE__, __LINE__);
  }

  return lyric;
}



static lyric_t *pick_right_lyric(struct client *client, fetchlyric_t *fl, const gchar *expect_encoding)
{
  const gchar *artist = NULL, *title = NULL;
  const gchar *x_artist, *x_title; 
  const gchar *path = NULL;
  lyric_t *lyric = NULL;
  metainfo_t *mi = NULL;

  while(( path = (client->get_music_file)(client->c_data)) == (const gchar *)-1); /* FUCX it! */
  DBGMSG("pick_right_lyric:path:%#p\n", path);
  DBGMSG("parse metainfo of %s\n", path ?: "(NULL)");
  if(path)  {
	mi = metainfo_get_metainfo(path, expect_encoding, 0);
	if(mi)  {
	  x_artist = metainfo_get_artist(mi);
	  x_title = metainfo_get_title(mi);
	  if(x_title)  {
		artist = g_strdup(x_artist);
		title = g_strdup(x_title);
		DBGMSG("extracted title info: %s\n", title);
		DBGMSG("extracted artist info: %s\n", artist ?: "(NULL)");
	  }
	}
  }

  if(! title)  {
	DBGMSG("failed to get meta info via metainfo, fall to media player.\n");
	if(mi)
	  metainfo_put_metainfo(mi);

	x_artist = (client->get_artist)(client->c_data);
	if(x_artist)
	  if(artist = zh_convert(x_artist, strlen(x_artist), NULL, expect_encoding))  {
		DBGMSG(" artist encoding convert ok: %s : %s\n", x_artist, artist);
		g_free((void *)x_artist);
	  }else  {
		printf("** failed to sanify artist encoding: %s\n", x_artist);
		artist = x_artist;
	  }

	x_title = (client->get_title)(client->c_data);
	if(x_title)
	  if(title = zh_convert(x_title, strlen(x_title), NULL, expect_encoding))  {
		DBGMSG("title encoding convert ok: %s : %s\n", x_title, title);
		g_free((void *)x_title);
	  }else  {
		printf("** failed to sanify title encoding: %s\n", x_title);
		title = x_title;
	  }

	if(! title)  {
	  g_free((void *)artist);
	  return NULL;
	}
  }

  lyric = __pick_right_lyric(fl, title, artist);

  g_free((void *)title);
  g_free((void *)artist);

  DBGMSG("get lyric %s\n", lyric ? "OK" : "Failed");

  return lyric;
}


static struct command cmd_list[];

static lyric_t *cmd_help(struct client *client, fetchlyric_t *fl, const gchar *arg)
{
  struct  command *cmd = cmd_list;
  gint len;

  printf("available commands:\n");
  while(cmd->short_cmd)  {
    len = strlen(cmd->long_cmd) + strlen(cmd->short_cmd);
    printf("\t%s[%s]%*s\t%s\n", cmd->long_cmd, cmd->short_cmd, 15 - len, "",  cmd->descript);
    cmd++;
  }

  return NULL;
}


static lyric_t *cmd_quit(struct client *client, fetchlyric_t *fl, const gchar *arg)
{
  if(exit_on_quit)
	(client->quit)(client->c_data);

  exit(0);
}


static lyric_t *cmd_shutdown(struct client *client, fetchlyric_t *fl, const gchar *arg)
{
  if(! (client->quit)(client->c_data))
	if(client->perror)  {
	  print_info("can't toggle:");
	  (client->perror)(client->c_data);
	}

  return NULL;
}

static lyric_t *cmd_start(struct client *client, fetchlyric_t *fl, const gchar *arg)
{
  if(! (client->play)(client->c_data))
	if(client->perror)  {
	  print_info("can't toggle:");
	  (client->perror)(client->c_data);
	}

  return NULL;
}

static lyric_t *cmd_lyric(struct client *client, fetchlyric_t *fl, const gchar *arg)
{
  lyric_t *l;
  l = pick_right_lyric(client, fl, "utf-8");
  fall_to_shell = FALSE;
  return l;
}


static lyric_t *cmd_search(struct client *client, fetchlyric_t *fl, const gchar *arg)
{
  gchar *title, *artist;
  gchar *yes;
  lyric_t *l;

  title = readline("Song Title:");
  g_strstrip(title);
  if(! title || ! *title)  {
	print_info("Title in searching is mandatory\n");
	return NULL;
  }

  artist = readline("Artist Name:");
  if(artist)
	g_strstrip(artist);
  l = __pick_right_lyric(fl, title, artist);
  g_free(title);
  g_free(artist);
  if(! l)  {
	print_info("No marching lyric found.\n");
	return NULL;
  }

  yes = readline("Use this lyric for the playing song[Y/n]:");
  if(yes)  {
	g_strstrip(yes);
	if(! strcasecmp(yes, "n") ||
	   ! strcasecmp(yes, "no") )  {
	  g_free(yes);
	  lyricparse_release_lyric(l);
	  return NULL;
	}
	g_free(yes);
  }

  fall_to_shell = FALSE;
  return l;
}


static lyric_t *cmd_next(struct client *client, fetchlyric_t *fl, const gchar *arg)
{
  if(! (client->next)(client->c_data))
	if(client->perror)  {
	  print_info("can't toggle:");
	  (client->perror)(client->c_data);
	}

  return NULL;
}

static lyric_t *cmd_prev(struct client *client, fetchlyric_t *fl, const gchar *arg)
{
  if(! (client->prev)(client->c_data))
	if(client->perror)  {
	  print_info("can't toggle:");
	  (client->perror)(client->c_data);
	}

  return NULL;
}


static lyric_t *cmd_file(struct client *client, fetchlyric_t *fl, const gchar *arg)
{
  gchar *file;

  if(! client->play_music_file)  {
	print_info("Sorry! Plugin or backend dosen't support this.\n");
	return NULL;
  }

  file = readline("File path:");
  if(! file)
	return NULL;
  g_strstrip(file);
  if(! *file)  {
	g_free(file);
	return NULL;
  }

  if(! (client->play_music_file)(client->c_data, file))
	if(client->perror)  {
	  print_info("can't toggle:");
	  (client->perror)(client->c_data);
	}

  g_free(file);
  return NULL;
}


static lyric_t *cmd_toggle(struct client *client, fetchlyric_t *fl, const gchar *arg)
{
  if(! (client->toggle)(client->c_data))
	if(client->perror)  {
	  print_info("can't toggle:");
	  (client->perror)(client->c_data);
	}

  return NULL;
}


static lyric_t *cmd_info(struct client *client, fetchlyric_t *fl, const gchar *arg)
{
  const gchar *artist, *title, *album, *year, *genre, *track, *comment;
  const gchar *file;
  metainfo_t *mi = NULL;

  file = (client->get_music_file)(client->c_data);
  if(file)
	mi = metainfo_get_metainfo(file, "utf-8", 0);
  if(mi)  {
	artist = metainfo_get_artist(mi);
	title = metainfo_get_title(mi);
	album = metainfo_get_album(mi);
	year = metainfo_get_year(mi);
	genre = metainfo_get_genre(mi);
	track = metainfo_get_track(mi);
	comment = metainfo_get_comment(mi);
  }else  {
	artist = (client->get_artist)(client->c_data);
	title = (client->get_title)(client->c_data);
	album = (client->get_album)(client->c_data);
	year = (client->get_year)(client->c_data);
	genre = (client->get_genre)(client->c_data);
	track = (client->get_track)(client->c_data);
	comment = (client->get_comment)(client->c_data);
  }

  printf("Song Info:\n");
  printf("TITLE: %s \t ARTIST: %s\n", title ?: "Unknown", artist ?: "Unknown");
  if(album)
	printf("ALBUM: %s", album);
  if(genre)
	printf("\tGENRE: %s", genre);
  if(track)
	printf("\tTRACK: %s", track);
  if(year)
	printf("\tYEAR: %s\n", year);
  if(comment)
	printf("COMMENT:\n\t%s\n", comment);
  putchar('\n');

  if(mi)  {
	g_free((void *)file);
	metainfo_put_metainfo(mi);
  }else  {
	g_free((void *)artist);
	g_free((void *)title);
	g_free((void *)album);
	g_free((void *)year);
	g_free((void *)genre);
	g_free((void *)track);
	g_free((void *)comment);
  }

  return NULL;
}


static struct command cmd_list[] = {
  {"help", "h", "print this help", cmd_help},
  {"quit", "q", "quit " PROG_NAME , cmd_quit},
  {"shutdown", "sh", "shutdown backends and quit", cmd_shutdown},
  {"start", "st", "start backends if not on", cmd_start},
  {"lyric", "ly", "automatically search lyric and display", cmd_lyric},
  {"search", "s", "search and display lyric according the given title and artist", cmd_search},

  {"next", "n", "play next song", cmd_next},
  {"prev", "p", "play previous song", cmd_prev},
  {"file", "f", "play specified file", cmd_file},
  {"toggle", "t", "toggle player between pause and play", cmd_toggle},
  {"info", "i", "show info about current playing song", cmd_info},
  //  {"volume", "v", "tune volume between 1 and 100, or show volume", cmd_volume},

  { NULL, NULL, NULL, NULL }
};


static lyric_t *dispatch_command(struct ui_data *ui_data)
{
  gchar *input, *cmd, *arg;
  struct command *command;
  struct client *client = ui_data->client;
  fetchlyric_t *fl = ui_data->lyric;
  lyric_t *lyric = NULL;


  while(1)  {
	input = readline(UI_CLI_PROMPT);
	if(! input)  {
	  if((client->status)(client->c_data) == PLAYER_STATUS_PAUSE)  {
		if((client->toggle)(client->c_data))
		  break;
		print_warn("failed to toggle player.\n");
	  }else
		break;
	  continue;
	}

	if(! *input)
	  continue;
	cmd = input;
	while(isspace(*cmd))	  cmd++;
	if(! *cmd)
	  continue;
	for(command = cmd_list; command->long_cmd || command->short_cmd; command++)  {
	  if(! strncmp(cmd, command->short_cmd, strlen(command->short_cmd)) )  {
		arg = cmd;
		arg += strlen(command->short_cmd);
		if(isspace(*arg) || ! *arg)  {
		  while(isspace(*++arg)   || *arg != '\0');
		  lyric = (command->func)(client, fl, arg);
		  add_history(input);
		  break;
		}
	  }
	  if(! strncmp(cmd, command->long_cmd, strlen(command->long_cmd)) )    {
		arg = cmd;
		arg += strlen(command->long_cmd);
		if(isspace(*arg) || ! *arg)  {
		  while(isspace(*++arg)   || *arg != '\0');
		  lyric = (command->func)(client, fl, arg);
		  add_history(input);
		  break;
		}
	  }
	}

	if(! command->long_cmd && ! command->short_cmd)
	  print_err("** bad command.\n");
	free(input);
	if(lyric)
	  return lyric;
  }

  return NULL;
}


static void __status_changed(void *data)
{
  DBGMSG("Player Status Changed!\n");
  magix++;
}

static void __player_exited(void *data)
{
  gboolean *exited = (gboolean *)data;

  *exited = FALSE;
}


static void __sighup_handler(int num)
{
  keep_rolling = FALSE;
  fall_to_shell = TRUE;
  magix++;

  signal(SIGHUP, SIG_DFL);
  signal(SIGINT, SIG_DFL);
}


static void    rollout_lyric(struct client *client, lyric_t *lyric, gint magic, const gchar *oldpath)
{
  char *text = NULL;
  gchar  *fpath = NULL;
  gint begin, end, pos;
  gint ret;

  keep_rolling = TRUE;
  signal(SIGHUP, __sighup_handler);
  signal(SIGINT, __sighup_handler);

  ret = setupterm(NULL, fileno(stdout), NULL);
  if(ret != OK) {
    fprintf(stderr,"*FATAL* can't setup term to display! aborting...");
	exit(1);
  }

  while(keep_rolling && magic == magix )  {
	pos = (client->time)(client->c_data);
	DBGMSG("current time pos: %d\n", pos);
	if(lyricparse_get_lyric(lyric, &text, &begin, &end, pos))  {
	  if(end == 0)  {
		end = (client->duration)(client->c_data);
		DBGMSG("current song end at: %d\n", end);
		print_moving_lyric(text, NORMAL_FG_COLOR, NORMAL_BG_COLOR, pos - begin, end - begin, magic);
		signal(SIGHUP, SIG_DFL);
		signal(SIGINT, SIG_DFL);
		break;
	  }else
		print_moving_lyric(text, FOREGROUND_COLOR, COLOR_BLACK, pos - begin, end - begin, magic);
	}
#ifndef NDEBUG
	else
	  DBGMSG("failed to get lyric text.\n");
#endif
	/* give the lib a chance to detect file switch... */
	fpath = (client->get_music_file)(client->c_data);

	if(fpath && oldpath)
	  if(strcmp(fpath, oldpath))  {
		g_free(fpath);
		break;
	  }
	g_free(fpath);
  }
}

static void __break_wait(int num)
{
  magix++;
  fall_to_shell = TRUE;

  signal(SIGHUP, SIG_DFL);
  signal(SIGINT, SIG_DFL);
}


 /* NOTE： if @oldpath  is not NULL,  it is freed before return  */
static void wait_for_next(struct client *client, gint magic, const gchar *oldpath)
{
  struct timespec ts;
  gchar *path = NULL;

  if(magic != magix)
	return;

  switch((client->status)(client->c_data))  {
  case PLAYER_STATUS_STOP:
	return;
  case PLAYER_STATUS_PAUSE:
	fall_to_shell = TRUE;
	return;
  }

  ts.tv_nsec = RESPONSE_TIME * 10 * 1000000;
  ts.tv_sec = 0;
  ts.tv_sec += ts.tv_nsec / 1000000000;
  ts.tv_nsec = ts.tv_nsec % 1000000000;

  signal(SIGHUP, __break_wait);
  signal(SIGINT, __break_wait);

  while(path = (client->get_music_file)(client->c_data))  {
	if(! oldpath)  {
	  oldpath = path;
	  continue;
	}

	DBGMSG("GET PATH: %s\n", path);
	if(strcmp(path, oldpath))  {
	  g_free(path);
	  break;
	}

	g_free(path);
	if(magic != magix)
	  break;

	nanosleep(&ts, NULL);
  }

  g_free((void *)oldpath);

  signal(SIGHUP, SIG_DFL);
  signal(SIGINT, SIG_DFL);
}


static gint ui_cli_run(void *data)
{
  struct ui_data *ui_data = (struct ui_data *)data;
  struct client *client;
  fetchlyric_t *fl;
  lyric_t *lyric;
  gint magic;
  gboolean backends_on = TRUE;
  gchar *oldpath = NULL;

  if(! data)
    return -1;
  client = ui_data->client;
  fl = ui_data->lyric;

  using_history();

  if((client->status)(client->c_data) != PLAYER_STATUS_PLAY)
	(client->play)(client->c_data);

  (client->set_file_changed)(client->c_data, __status_changed, NULL);
  (client->set_player_stoped)(client->c_data, __status_changed, NULL);
  (client->set_player_exited)(client->c_data, __player_exited, &backends_on);

  while(backends_on)  {
	if((client->status)(client->c_data) == PLAYER_STATUS_PAUSE)
	  fall_to_shell = TRUE;

	if( fall_to_shell)  {
	  fall_to_shell = FALSE;
	  lyric = dispatch_command((struct ui_data *)data);
	}else  {
	  lyric = pick_right_lyric(client, fl, "utf-8");
	  if(! lyric)
		if(wait_if_no_lyric)
		  fprintf(stderr, "\nNo lyric found, press Ctrl-C to to find it yourself.");
		else
		  lyric = dispatch_command((struct ui_data *)data);
	}

	oldpath = (client->get_music_file)(client->c_data);
	magic = magix;
	if(lyric)  {
	  rollout_lyric(client, lyric, magic, oldpath);
	  lyricparse_release_lyric(lyric);
	}
	wait_for_next(client, magic, oldpath);
  }

  return 0;
}


static gint ui_cli_cleanup(void *data)
{
  struct ui_data *ui_data = (struct ui_data *)data;

  if(! data)
    return FALSE;

  if(ui_data->client)
    if(! unload_client(ui_data->client->c_servername, ui_data->client))  {
      fprintf(stderr, "** Failed to unload plugin %s.\n", ui_data->client->c_servername);
      return FALSE;
    }

  if(ui_data->config)
    free_config(ui_data->config);

  fetch_lyric_unload_all(& ui_data->lyric);

  return TRUE;
}



struct  ui_descrip UI_EXPORT_SYMBOL =  {
  .ui_init = ui_cli_init,

  .name = "cli",
  .description = "command line interface plugin displaying lyric dynamicly",
  .author = "X Q",
  .mail = "crs.chin@gmail.com",
  .version = "0.1"
};





/////////////////////////////////////////////////////////
/////////////////// testing part ////////////////////////
/////////////////////////////////////////////////////////

#if defined(TEST_LDISP)


#include <pthread.h>
#include <readline/readline.h>

void *thread1(void *data)
{
  print_moving_lyric("May it be an envening star shines upon you",
		     NORMAL_FG_COLOR,NORMAL_BG_COLOR,0.8,2.0);
  print_moving_lyric("May it be when darkness fall,your heart will be true",
		     NORMAL_FG_COLOR,NORMAL_BG_COLOR,0,2.0);
  print_moving_lyric("you walk a lonly road,oh how far you are from home.",
		     NORMAL_FG_COLOR,NORMAL_BG_COLOR,0,2.0);
  print_moving_lyric("May it be an envening star shines upon you",
		     NORMAL_FG_COLOR,NORMAL_BG_COLOR,0,2.0);
  print_moving_lyric("what i've done,what the hell is this.",
		     NORMAL_FG_COLOR,NORMAL_BG_COLOR,0,1.50);

  /*   print_moving_lyric("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", */
  /* 		     NORMAL_FG_COLOR,NORMAL_BG_COLOR,1.49,1.50); */

  //  fflush(stdout);
}


int main()
{
  int ret,i;
  pthread_t thread;
  char *status;

  ret=setupterm(NULL,fileno(stdout),NULL);
  if(ret!=OK) {
    puts("**error setup term.");
    return 1;
  }
  status=malloc(COLUMNS);

  pthread_create(&thread,NULL,thread1,NULL);

  status=readline("xxxxxxxxxxx->");
  pthread_join(thread,NULL);
  /*   for(i=1;i<COLUMNS;i++) { */
  /*     status[i]='A'+i%16; */
  /*     putchar('\r'); */
  /*     printf(status); */
  /*     sleep(1); */
  /*   } */
 
}

#endif
